/*
 * Scaler.java
 *
 * Created on December 16, 2007, 3:42 PM
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package base;

import java.util.ArrayList;
import java.util.List;
import tal.drivechain.matchers.VarMatcher;
import tal.drivechain.parsing.Range;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;

import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.SetProperty;
import tal.drivechain.variable.Variable;

/**
 *
 * @author Main
 */
public class Scaler extends Module {
    private List<Object[]> triggerRange = null;
    private List<RangeScaler> scalers = new ArrayList<RangeScaler>();
    private Variable out = new Variable("scaler", DataType.FLOAT, 0, true);

    public void setTriggerRange(List<Object[]> value, SetProperty.Flag flag) {
        if (flag == SetProperty.Flag.ADD) {
            if (triggerRange==null) triggerRange = new ArrayList<Object[]>();
            triggerRange.addAll(value);
        }
        else triggerRange = value;

        
        scalers.clear();
        for (Object[] tR : triggerRange) {
            Range inR = (Range)((Object[])(Object[])tR[1])[0];
            double[] ordIn = inR.getOrderedRange();
            Range outR = (Range)((Object[])(Object[])tR[1])[1];
            double[] ordOut = outR.getOrderedRange();
            scalers.add(new RangeScaler(ordIn[0], ordIn[1], ordOut[0], ordOut[1]));
        }

    } public Object getTriggerRange() { return triggerRange; }
    
    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
        if (channel!=Channel.CHAIN) return;
        for (int i=0; i<triggerRange.size(); i++) {
            Object[] item = triggerRange.get(i);
            VarMatcher trigger = (VarMatcher)item[0];
            if (trigger.matches(in, channel, this)) {
                RangeScaler range = scalers.get(i);
                double scaled = range.scaleValue((Double)DataType.FLOAT.cast(in.getValue(this)));
                out.setValue(scaled, true);
                sendToOutputs(out, -1);
            }
        }
    }
}

class RangeScaler {
    private double inmin; // the minimum value for input
    private double inmax; // the maximum value for input
    private double outmin; // the minimum value for output
    private double outmax; // the max value for output
    private double mult; //  internal parameter for fast calculations
    private double r; // the same as mult
    
    public RangeScaler(double inmin, double inmax, double outmin, double outmax) {
        this.inmin = inmin;
        this.inmax = inmax;
        this.outmin = outmin;
        this.outmax = outmax;
        mult = (outmax-outmin)/(inmax-inmin);
        r = outmax-mult*inmax;
    }
    
    public double scaleValue(double in) throws IllegalArgumentException {
        double min = Math.min(inmax, inmin);
        double max = Math.max(inmax, inmin);
        if (in>max) {
            in = max;
        }
        else if (in<min) {
            in = min;
        }
        return r + mult*in;
    }
    
    public static RangeScaler parseRangeDef(String def) throws IllegalArgumentException {
        // inmax..inmin,outmax..outmin
        int colonIdx = def.indexOf(',');
        if (colonIdx==-1) throw new IllegalArgumentException("Missing colon (,) in ranges declaration: (" + def + ")");
        double[] min = new Range(def.substring(0, colonIdx), Range.Type.CLOSED_ONLY).getRange();
        double[] max = new Range(def.substring(colonIdx+1), Range.Type.CLOSED_ONLY).getRange();
        return new RangeScaler(min[0], min[1], max[0], max[1]);
    }
}

